package com.hp.TrimClient;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.hp.TrimClient.Enums.UserPermissions;
import com.hp.TrimClient.MainObjects.Location;
import com.hp.TrimClient.SdkObjects.IPageItem;
import com.hp.TrimClient.SdkObjects.PageItem;
import com.hp.TrimClient.SdkObjects.PageItemField;
import com.hp.TrimClient.SdkObjects.PageItemProperty;
import com.hp.TrimClient.SdkObjects.PermissionsDictionary;

public class TrimJson {

	private static final String FIELD_DECIMAL_REGEX = "HP\\.HPTRIM\\.ServiceModel\\.TrimProperty`1\\[\\[System.Decimal, mscorlib\\]\\], HP\\.HPTRIM\\.(.*)";
	// private static final String FIELD_DECIMAL =
	// "HP\\.HPTRIM\\.ServiceModel\\.TrimProperty`1[[System.Decimal, mscorlib]], HP\\.HPTRIM\\.ServiceModel";
	private static final String FIELD_STRING_REGEX = "HP\\.HPTRIM\\.ServiceModel\\.TrimStringProperty, HP\\.HPTRIM\\.(.*)";
	private static final String FIELD_INT_REGEX = "HP\\.HPTRIM\\.ServiceModel\\.TrimProperty`1\\[\\[System.Int32, mscorlib\\]\\], HP\\.HPTRIM\\.(.*)";
	private static final String FIELD_LONG_REGEX = "HP\\.HPTRIM\\.ServiceModel\\.TrimProperty`1\\[\\[System.Int64, mscorlib\\]\\], HP\\.HPTRIM\\.(.*)";
	private static final String FIELD_BOOL_REGEX = "HP\\.HPTRIM\\.ServiceModel\\.TrimProperty`1\\[\\[System.Boolean, mscorlib\\]\\], HP\\.HPTRIM\\.(.*)";
	private static final String FIELD_DATE_REGEX = "HP\\.HPTRIM\\.ServiceModel\\.TrimDateTime, HP\\.HPTRIM\\.(.*)";
	private static final String FIELD_LOCATION_REGEX = "HP\\.HPTRIM\\.ServiceModel.LocationRef, HP\\.HPTRIM\\.(.*)";

	private static final String FIELD_DECIMAL = "HP.HPTRIM.ServiceModel.TrimProperty`1[[System.Decimal, mscorlib]], HP.HPTRIM.ServiceModel";
	// private static final String FIELD_DECIMAL =
	// "HP\\.HPTRIM\\.ServiceModel\\.TrimProperty`1[[System.Decimal, mscorlib]], HP\\.HPTRIM\\.ServiceModel";
	private static final String FIELD_STRING = "HP.HPTRIM.ServiceModel.TrimStringProperty, HP.HPTRIM.ServiceModel";
	private static final String FIELD_INT = "HP.HPTRIM.ServiceModel.TrimProperty`1[[System.Int32, mscorlib]], HP.HPTRIM.ServiceModel";
	private static final String FIELD_LONG = "HP.HPTRIM.ServiceModel.TrimProperty`1[[System.Int64, mscorlib]], HP.HPTRIM.ServiceModel";
	private static final String FIELD_BOOL = "HP.HPTRIM.ServiceModel.TrimProperty`1[[System.Boolean, mscorlib]], HP.HPTRIM.ServiceModel";
	private static final String FIELD_DATE = "HP.HPTRIM.ServiceModel.TrimDateTime, HP.HPTRIM.ServiceModel";
	private static final String FIELD_LOCATION = "HP.HPTRIM.ServiceModel.LocationRef, HP.HPTRIM.ServiceModel";

	private static final String PAGE_ITEM_FIELD_TYPE = "HP.HPTRIM.ServiceModel.PageItemField, HP.HPTRIM.ServiceAPI.Model";
	private static final String PAGE_ITEM_PROPERTY_TYPE = "HP.HPTRIM.ServiceModel.PageItemProperty, HP.HPTRIM.ServiceAPI.Model";

	private HashMap<Class<?>, String> _fieldTypeMap = new HashMap<Class<?>, String>();

	private class DateTimeSerializer implements JsonSerializer<Date> {

		@Override
		public JsonElement serialize(Date src, Type typeOfSrc,
				JsonSerializationContext context) {

			if (src != null) {
				SimpleDateFormat formatter = new SimpleDateFormat(
						"yyyy-MM-dd'T'HH:mm:ss.SXXX");
				return new Gson().toJsonTree(formatter.format(src));
			}
			return null;
		}

	}

	private class DateTimeDeserializer implements JsonDeserializer<Date> {
		public Date deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {

			SimpleDateFormat formatter = new SimpleDateFormat(
					"yyyy-MM-dd'T'HH:mm:ss.s");

			try {

				return formatter.parse(json.getAsJsonPrimitive().getAsString());
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return null;
		}
	}

	private class FieldSerializer implements JsonSerializer<FieldCollection> {
		@Override
		public JsonElement serialize(FieldCollection src, Type typeOfSrc,
				JsonSerializationContext context) {
			if (src != null) {
				TrimJson trimJson = new TrimJson();
				JsonObject jObject = new JsonObject();
				for (Entry<String, TrimProperty> trimProperty : src.entrySet()) {
					JsonElement jel = trimJson.toJsonTree(trimProperty
							.getValue());

					String typeString;

					TrimProperty propVal = trimProperty.getValue();
					typeString = _fieldTypeMap.get(propVal.getTrimClass());

					((JsonObject) jel).addProperty("__type", typeString);
					jObject.add(trimProperty.getKey(), jel);

				}

				return jObject;
			}
			return null;
		}
	}

	private class PermissionsDeserializer implements
			JsonDeserializer<PermissionsDictionary> {

		public PermissionsDictionary deserialize(JsonElement json,
				Type typeOfT, JsonDeserializationContext context)
				throws JsonParseException {

			PermissionsDictionary permissions = new PermissionsDictionary();
			if (json.isJsonObject()) {
				JsonObject jObject = json.getAsJsonObject();
				for (Map.Entry<String, JsonElement> entry : jObject.entrySet()) {

					permissions.put(UserPermissions.valueOf(entry.getKey()),
							entry.getValue().getAsBoolean());

				}
			}

			return permissions;
		}

	}

	private class TrimUserOptionsSetDeserializer implements
			JsonDeserializer<TrimUserOptionsSet> {
		public TrimUserOptionsSet deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {

			if (json.isJsonObject()) {
				TrimJson trimJson = new TrimJson();

				JsonObject jObject = json.getAsJsonObject();
				String typeName = jObject.get("__type").getAsString();
				typeName = "com.hp.TrimClient.UserOptions."
						+ typeName.substring(
								typeName.lastIndexOf("ServiceModel.") + 13,
								typeName.lastIndexOf(","));

				try {
					return (TrimUserOptionsSet) trimJson.fromJson(json,
							Class.forName(typeName));
				} catch (ClassNotFoundException e) {
					throw new JsonParseException("Unable to deserialize "
							+ typeName);
				}

			}

			return null;
		}
	}

	private class IPageItemSerializer implements JsonSerializer<IPageItem> {

		private JsonElement makePageItem(IPageItem src, String typeString) {
			TrimJson trimJson = new TrimJson();

			JsonElement jsonProperty = trimJson.toJsonTree(src, src.getClass());

			JsonObject returnObject = new JsonObject();

			returnObject.addProperty("__type", typeString);

			for (Map.Entry<String, JsonElement> entry : ((JsonObject) jsonProperty)
					.entrySet()) {
				returnObject.add(entry.getKey(), entry.getValue());
			}

			return returnObject;
		}

		@Override
		public JsonElement serialize(IPageItem src, Type typeOfSrc,
				JsonSerializationContext context) {
			if (src != null) {
				if (src instanceof PageItemProperty) {
					return makePageItem(src, PAGE_ITEM_PROPERTY_TYPE);
				} else {
					return makePageItem(src, PAGE_ITEM_FIELD_TYPE);
				}

			}
			return null;
		}
	}

	private class IPageItemDeserializer implements JsonDeserializer<IPageItem> {

		public PageItem deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {

			if (json.isJsonObject()) {
				TrimJson trimJson = new TrimJson();

				JsonObject jObject = json.getAsJsonObject();
				if (jObject.get("__type").getAsString()
						.contains("PageItemProperty")) {

					return (PageItemProperty) trimJson.fromJson(json,
							PageItemProperty.class);

				} else {

					return (PageItemField) trimJson.fromJson(json,
							PageItemField.class);
				}

			}

			throw new JsonParseException("Page Item not valid");
		}

	}

	private class FieldDeserializer implements
			JsonDeserializer<FieldCollection> {

		private Boolean matchesType(String pattern, String typeName) {
			Pattern r = Pattern.compile(pattern);
			Matcher m = r.matcher(typeName);

			return m.find();
		}

		private TrimProperty makeProperty(JsonObject mapProp) {

			if (mapProp.has("__type")) {
				String typeName = mapProp.get("__type").getAsString();

				if (matchesType(FIELD_DATE_REGEX, typeName)) {
					TrimJson trimJson = new TrimJson();
					return (TrimProperty) trimJson.fromJson(mapProp,
							TrimDateTime.class);
				}

				if (matchesType(FIELD_LOCATION_REGEX, typeName)) {
					TrimJson trimJson = new TrimJson();
					return (TrimProperty) trimJson.fromJson(mapProp,
							Location.class);
				}

				JsonElement rawVal = mapProp.get("Value");

				if (matchesType(FIELD_STRING_REGEX, typeName)) {
					return new DefaultTrimProperty<String>(rawVal.getAsString());
				}

				if (matchesType(FIELD_DECIMAL_REGEX, typeName)) {
					return new DefaultTrimProperty<Double>(rawVal.getAsDouble());
				}

				if (matchesType(FIELD_INT_REGEX, typeName)) {
					return new DefaultTrimProperty<Integer>(rawVal.getAsInt());
				}

				if (matchesType(FIELD_LONG_REGEX, typeName)) {
					return new DefaultTrimProperty<Long>(rawVal.getAsLong());
				}

				if (matchesType(FIELD_BOOL_REGEX, typeName)) {
					return new DefaultTrimProperty<Boolean>(
							rawVal.getAsBoolean());
				}

			}
			return null;
		}

		public FieldCollection deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {

			FieldCollection fieldCollection = new FieldCollection();

			if (json.isJsonObject()) {
				JsonObject jObject = json.getAsJsonObject();
				for (Map.Entry<String, JsonElement> entry : jObject.entrySet()) {
					JsonObject mapProp = (JsonObject) entry.getValue();

					TrimProperty trimProp = makeProperty(mapProp);

					if (mapProp.has("StringValue")) {
						trimProp.setStringValue(mapProp.get("StringValue")
								.getAsString());
					}

					if (trimProp != null) {
						fieldCollection
								.put(entry.getKey().toString(), trimProp);
					}
				}
			}

			return fieldCollection;

		}
	}

	private Gson gson;

	public TrimJson() {
		_fieldTypeMap.put(Location.class, FIELD_LOCATION);
		_fieldTypeMap.put(Integer.class, FIELD_INT);
		_fieldTypeMap.put(Long.class, FIELD_LONG);
		_fieldTypeMap.put(TrimDateTime.class, FIELD_DATE);
		_fieldTypeMap.put(Double.class, FIELD_DECIMAL);
		_fieldTypeMap.put(Boolean.class, FIELD_BOOL);
		_fieldTypeMap.put(String.class, FIELD_STRING);

		GsonBuilder gsonBuilder = new GsonBuilder();

		gsonBuilder.registerTypeAdapter(Date.class, new DateTimeDeserializer());
		gsonBuilder.registerTypeAdapter(Date.class, new DateTimeSerializer());
		gsonBuilder.registerTypeAdapter(FieldCollection.class,
				new FieldDeserializer());
		gsonBuilder.registerTypeAdapter(FieldCollection.class,
				new FieldSerializer());
		gsonBuilder.registerTypeAdapter(PermissionsDictionary.class,
				new PermissionsDeserializer());

		gsonBuilder.registerTypeAdapter(IPageItem.class,
				new IPageItemDeserializer());
		gsonBuilder.registerTypeAdapter(IPageItem.class,
				new IPageItemSerializer());

		gsonBuilder.registerTypeAdapter(TrimUserOptionsSet.class,
				new TrimUserOptionsSetDeserializer());

		gson = gsonBuilder.create();
	}

	public <TResponse> TResponse fromJson(String json, Type typeOfT) {
		return gson.fromJson(json, typeOfT);
	}

	public Object fromJson(JsonElement json, Type typeOfT) {
		return gson.fromJson(json, typeOfT);
	}

	public String toJson(Object obj) {
		return gson.toJson(obj);
	}

	public JsonElement toJsonTree(Object obj) {
		return gson.toJsonTree(obj);
	}

	public JsonElement toJsonTree(Object obj, Type typeOfSrc) {
		return gson.toJsonTree(obj, typeOfSrc);
	}

	public WebServiceException makeHttpEx(String body) {

		TrimErrorResponseStatus responseStatus = gson.fromJson(body,
				TrimErrorResponseStatus.class);
		return new WebServiceException(responseStatus.getResponseStatus()
				.getMessage());

	}

}
